
#include <stdio.h>

#include "gd32f30x.h"
#include "i2c_master.h"
#include "BSP.h"
#include "IO.h"

#define SI2C_CLK_Time	(2 + 15)
#define SI2C_Data_Time	(1 + 15)


static const IO_T si2cSDA = IO_INIT_TABLE(GPIOB, GPIO_PIN_9);
static const IO_T si2cSCL = IO_INIT_TABLE(GPIOB, GPIO_PIN_8);

static void SI2C_Stop(void){
	IO_Set(&si2cSCL, 0);
	IO_Init(&si2cSDA, IO_Mode_OD);
	IO_Set(&si2cSDA, 0);
	BSP_Delay_us(SI2C_CLK_Time);
	IO_Set(&si2cSCL, 1);
	BSP_Delay_us(SI2C_CLK_Time);
	IO_Set(&si2cSDA, 1);
	BSP_Delay_us(SI2C_CLK_Time);
}

static void SI2C_Start(void){
	IO_Set(&si2cSCL, 1);
	IO_Set(&si2cSDA, 1);
	BSP_Delay_us(SI2C_CLK_Time);
	IO_Set(&si2cSDA, 0);
	BSP_Delay_us(SI2C_CLK_Time);
	IO_Set(&si2cSCL, 0);
}


#define SI2C_GetACK(bit)	do{ 						\
		IO_Set(&si2cSCL, 0);							\
		IO_Set(&si2cSDA, 1);							\
		BSP_Delay_us(SI2C_Data_Time);					\
		BSP_Delay_us(SI2C_Data_Time);					\
		IO_Set(&si2cSCL, 1);							\
		BSP_Delay_us(SI2C_Data_Time);					\
		if(IO_Get(&si2cSDA)) bit = 1;					\
		else bit = 0;								\
		BSP_Delay_us(SI2C_Data_Time);					\
		IO_Set(&si2cSDA, bit);							\
		IO_Set(&si2cSCL, 0);							\
		BSP_Delay_us(SI2C_Data_Time);					\
		BSP_Delay_us(SI2C_Data_Time);					\
		IO_Set(&si2cSDA, 1);							\
	}while(0)
	
#define SI2C_SetACK(bit)	do{ 						\
		IO_Set(&si2cSDA, IO_Get(&si2cSDA));				\
		IO_Set(&si2cSCL, 0);							\
		IO_Set(&si2cSDA, bit);							\
		BSP_Delay_us(SI2C_Data_Time);					\
		BSP_Delay_us(SI2C_Data_Time);					\
		IO_Set(&si2cSCL, 1);							\
		BSP_Delay_us(SI2C_Data_Time);					\
		BSP_Delay_us(SI2C_Data_Time);					\
		IO_Set(&si2cSCL, 0);							\
		BSP_Delay_us(SI2C_Data_Time);					\
		IO_Set(&si2cSDA, 1);							\
		BSP_Delay_us(SI2C_Data_Time);					\
	}while(0)
	
#define SI2C_InBit(byte, bit)	do{uint8_t _bit;		\
		IO_Set(&si2cSCL, 0);							\
		BSP_Delay_us(SI2C_Data_Time);					\
		BSP_Delay_us(SI2C_Data_Time);					\
		IO_Set(&si2cSCL, 1);							\
		BSP_Delay_us(SI2C_Data_Time);					\
		_bit = IO_Get(&si2cSDA);						\
		if(_bit) {byte |= (1u << (bit));}				\
		BSP_Delay_us(SI2C_Data_Time);					\
		IO_Set(&si2cSCL, 0);							\
		IO_Set(&si2cSDA, 1); 							\
	}while(0)


#define SI2C_OutBit(byte, bit)	do{					\
	IO_Set(&si2cSCL, 0);							\
	IO_Set(&si2cSDA, (byte) & (1u << (bit)));		\
	BSP_Delay_us(SI2C_Data_Time);					\
	BSP_Delay_us(SI2C_Data_Time);					\
	IO_Set(&si2cSCL, 1);							\
	BSP_Delay_us(SI2C_Data_Time);					\
	BSP_Delay_us(SI2C_Data_Time);					\
	IO_Set(&si2cSCL, 0);							\
}while(0)

static uint8_t SI2C_SendByte(uint8_t byte){
	uint8_t i = 8;
	uint8_t ack;

	while(i > 0){
		--i;
		SI2C_OutBit(byte, i);
	}
	SI2C_GetACK(ack);
	return ack;
}

static uint8_t SI2C_ReceiveByte(uint8_t ack){
	uint8_t byte = 0;
	uint8_t i = 8;

	while(i > 0){
		--i;
		SI2C_InBit(byte, i);
	}
	
	SI2C_SetACK(ack);
	return byte;
}


static uint8_t SI2C_DoWrite(uint8_t devaddr, uint8_t regaddr, uint8_t* buffer, uint16_t length){
	uint16_t i;

	if(SI2C_SendByte(devaddr)) 
		return 1;
	if(SI2C_SendByte(regaddr)) 
		return 1;
	for(i = 0; i < length; ++i){
		if(SI2C_SendByte(buffer[i])){
			return 1;
		}
	}
	return 0;
}

static uint8_t SI2C_DoRead(uint8_t devaddr, uint8_t* buffer, uint16_t length){
	uint16_t i;

	if(SI2C_SendByte(devaddr)) {
		return 1;
	}
	if(length == 0){
		return 0;
	}
	
	for(i = 0; i < length - 1; ++i){
		buffer[i] = SI2C_ReceiveByte(0);
	}
	buffer[i] = SI2C_ReceiveByte(0);
	return 0;
}


static uint8_t SI2C_Write(uint8_t devaddr, uint8_t regaddr, uint8_t* buffer, uint16_t length){
	uint8_t ret;
	
	SI2C_Start();
	ret = SI2C_DoWrite(devaddr, regaddr, buffer, length);
	SI2C_Stop();
	return ret;
}

static uint8_t SI2C_Read(uint8_t devaddr, uint8_t regaddr, uint8_t* buffer, uint16_t length){
	uint8_t ret;
	
	SI2C_Start();
	ret = SI2C_DoWrite(devaddr & 0xFE, regaddr, buffer, 0);
	if(!ret){
		SI2C_Start();
		ret = SI2C_DoRead(devaddr | 0x01, buffer, length);
	}
	SI2C_Stop();
	return ret;
}


uint8_t i2c_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* buffer, uint16_t length)
{
	uint8_t ret;
	
	ret = SI2C_Write(devaddr, regaddr, buffer, length);
	return ret;
}


uint8_t i2c_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* buffer, uint16_t length)
{  
	uint8_t ret;
	ret =  SI2C_Read(devaddr, regaddr, buffer, length);	
	return ret;
}

void i2c_init(void)
{	
	IO_Init(&si2cSDA, IO_Mode_OD);
	IO_Init(&si2cSCL, IO_Mode_OUT);

	SI2C_Stop();
}


